#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
__title__ = ''
__author__ = 'Box'
__mtime__ = '20210312'
"""
from copy import copy, deepcopy
import numpy as np
import pandas as pd
import multiprocessing
from typing import Callable
from random import random
import scipy as sp


class PVType:
    """峰谷类型"""
    # 根据窗口长度windows对价格峰谷的确认: 峰、谷间最小相隔windows个周期，称为周期峰谷，记为 WPV
    """ 周期峰 WP 的定义
    给定窗口长度 w,T-时刻的价格 P0 ,如果存在 x>=w,y>=w，
    记：LL0=LLV(P[T-x,T]),HH0=HHV(P[T-x,T]),
    LL1=LLV(P[T,T+y]), HH1=HHV(P[T,T+y]),
    P0称为周期峰，满足：
    1、P0=HHV(P[T-x,T+y]),
    2、P[T-x]==LL0,P[T+y]==LL1
    距离前后谷的周期数不小于 w
    周期峰的确认：距离前期高点的周期数 intervals >=w 的最近低点(=HHV(price,intervals))
    存在准确认的问题：即周期间隔==w时，价格不是最低价（指从近高开始）
     周期峰谷 WV 的定义
    T-时刻的价格 P 称为周期谷，满足：
    1、P=LLV(P[T-w,T+w])
    2、距离前后峰的周期数不小于 w
    """
    QH=1003 # 前半高，p0==HHV(P[t0,t0-w])
    HQ=1005 # 后半高， p0==HHV(P[t0,t0+w])
    HH = 1001  # 局部高（顶），给定周期内的最高点
    HHO = 1002  # 局部高确认
    LL = -1001  # 局部低（底），给定周期内的最低点
    LLO = -1002  # 局部低确认
    WPI = 11  # 峰
    QWPO = 13  # 仅周期数满足条件，此点不一定是局部底
    WPO = 12  # 峰确认，周期数满足条件
    WVI = -11  # 谷
    QWVO = -13  # 仅周期数满足条件
    WVO = -12  # 谷确认
    # 基于周期数的峰谷
    PVW = dict(WPI=11,  # 峰 
               WPO=12,  # 峰确认
               WVI=-11,  # 谷
               WVO=-12,  # 谷确认
               )
    # 根据涨跌幅度 rng 对价格峰谷的确认：(p-v)/(p+v)*2*1000>= rng，称为价格峰谷，记为 PPV
    PPI = 101  # 价格峰 Price Peak Is
    PPO = 102  # 价格峰确认 Price Peak Ok
    PVI = -101  # 价格谷 Price Valley Is
    PVO = -102  # 价格谷确认 Price Valley Ok
    PVR = dict(PPI=101,  # 峰
               PPO=102,  # 峰确认
               PVI=-101,  # 谷
               PVO=-102,  # 谷确认
               )
    # 根据窗口长度w和涨跌幅度r对价格的分类
    P = 1  # 峰
    PO = 2  # 峰确认
    QPO = 3  # 满足P的条件，但不是局部低, 前一标志是PPO，此时满足了周期间隔条件
    V = -1  # 谷
    VO = -2  # 谷确认
    QVO = -3  # 满足V的条件，但不是局部高

    HHH = 4  # 新高
    LLL = -4  # 新低

    PVWR = dict(P=1,  # 峰
                PO=2,  # 峰确认
                V=-1,  # 谷
                VO=-2,  # 谷确认
                )
    UN = 0  # 未确认


class TrendType:
    """趋势类型"""
    SS = 0  # 震荡
    UP = 200  # 上涨
    DN = -200  # 下跌
    SUP = 100  # 震荡上行
    SDN = -100  # 震荡下行
    value: float = 0  # -200到200 之间
    strength: float = 0  # 强度-100－100


class NewHighLow:
    """创新高、新低"""
    H = 1
    L = -1
    C = 0


class HLRelationship:
    # 状态
    SSC = 100  # 震荡
    UP = 200  # 上涨
    DN = 300  # 下跌
    """HH LL 间的关系"""
    # 根据窗口长度windows对价格峰谷的确认: 峰、谷间最小相隔windows个周期
    WPI = 11  # 峰
    WPO = 12  # 峰确认
    WVI = -11  # 谷
    WVO = -12  # 谷确认
    PVW = dict(WPI=11,  # 峰
               WPO=12,  # 峰确认
               WVI=-11,  # 谷
               WVO=-12,  # 谷确认
               )
    # 根据涨跌幅度 rng 对价格峰谷的确认：(p-v)/(p+v)*2*1000>= rng
    PPI = 101  # 峰
    PPO = 102  # 峰确认
    PVI = -101  # 谷
    PVO = -102  # 谷确认
    PVR = dict(PPI=101,  # 峰
               PPO=102,  # 峰确认
               PVI=-101,  # 谷
               PVO=-102,  # 谷确认
               )
    # 根据窗口长度w和涨跌幅度r对价格的分类
    P = 1  # 峰
    PO = 2  # 峰确认
    V = -1  # 谷
    VO = -2  # 谷确认

    PVWR = dict(P=1,  # 峰
                PO=2,  # 峰确认
                V=-1,  # 谷
                VO=-2,  # 谷确认
                )

    PCUN = None  # 未分类
    SS = 0  # 盘整或震荡 State of Shock
    SUI = 1  # 盘升 Shock Up In rng
    SU = 2  # 盘升 Shock Up out rng and new SS
    SDI = -1  # 盘降 Shock Down In
    SD = -2  # 盘降 Shock Down
    UB = 4  # 升始 Up Begin
    UL = 8  # 升续 Up Last
    UE = -16  # 升结 Up End
    DB = -4  # 降始 Down Begin
    DL = -8  # 降续
    DE = 16  # 降结
    PC = dict(SS=0,  # 盘整或震荡 State of Shock
              SUI=1,  # 盘升 Shock Up In rng
              SU=2,  # 盘升 Shock Up out rng and new SS
              SDI=-1,  # 盘降 Shock Down In
              SD=-2,  # 盘降 Shock Down
              UB=4,  # 升始 Up Begin
              UL=8,  # 升续 Up Last
              UE=-16,  # 升结 Up End
              DB=-4,  # 降始 Down Begin
              DL=-8,  # 降续
              DE=16,  # 降结

              )
    # 前后两个高或两个低间的关系
    HHG = 4  # 后高大于前高
    HHL = -1  # 后高小于前高
    HHE = 0
    LLG = 11  # 后低大于前低
    LLL = -14  # 后低小于前低
    LLE = 0
    # 同一周期连续3个价格（p1,p2,p3)（3个顶或底）间的关系:S=SMALL,M=MEDIAN,B=BIG,E=EQUAL
    SMB = 1  # p1<p2<p3
    EEB = 2  # p1=p2<p3
    MSB = 3  # p2<p1<p3
    SBM = 4  # p1<p3<p2
    MBS = 5  # p3<p1<p2
    EBE = 6  # p1=p3<p2
    BSM = 7  # p2<p3<p1
    BMS = 8  # p3<p2<p1
    BEE = 9  # p2=p3<p1
    SEE = 10  # P1<P2=P3
    ESE = 11  # P2<P1=P3
    EES = 12  # P3<P1=P2
    EEE = 13  # P1=P2=P3
    # 连续三个价格间的关系：S: SMALL, M: MEDIAN, B: BIG
    P3 = dict(SMB=1,  # p1<p2<p3
              EEB=2,  # p1=p2<p3
              MSB=3,  # p2<p1<p3
              SBM=4,  # p1<p3<p2
              MBS=5,  # p3<p1<p2
              EBE=6,  # p1=p3<p2
              BSM=7,  # p2<p3<p1
              BMS=8,  # p3<p2<p1
              BEE=9,  # p2=p3<p1
              SEE=10,  # P1<P2=P3
              ESE=11,  # P2<P1=P3
              EES=12,  # P3<P1=P2
              EEE=13,  # P1=P2=P3
              )
    # 同一周期连续4个价格（p1,p2,p3,p4)（顶或4个底）间的关系
    GGG = 1  # p1<p2<p3<p4 p1=p2<p3<p4 p1<p2=p3<p4 p1=p2=p3<p4
    EGG = 2  # p1<p2<p3<p4 p1=p2<p3<p4 p1<p2=p3<p4 p1=p2=p3<p4
    GEG = 3  # p1<p2<p3<p4 p1=p2<p3<p4 p1<p2=p3<p4 p1=p2=p3<p4
    EEG = 4  # p1<p2<p3<p4 p1=p2<p3<p4 p1<p2=p3<p4 p1=p2=p3<p4
    LG2 = 5  # p2<p1<p3<p4 p2<p1=p3<p4
    LEG = 6  # p2<p1<p3<p4 p2<p1=p3<p4
    L2E = 7  # p1<p2<p3=p4

    H4R = dict(H1GGG=0b1000,
               H1GLG=0b0100,
               H1GLL=0b0010,
               H1GLE=8 - 4,
               H1EGG=4 + 2,
               H1ELL=-4 - 2,
               H1EEE=0,
               H1LLL=-8 - 4 - 2,
               H1LGL=-8 + 4 - 2,
               H1LGG=-8 + 4 + 2,
               H1LGE=-8 + 4)
    # 不同周期4个同类间的关系
    H2G1 = -2
    H3G2 = -4
    H4G3 = -8
    H2E1 = 2
    H3E2 = 4
    H4E3 = 8
    L1G2 = 102
    L2G3 = 104
    L3G4 = 108
    L1E2 = -102
    L2E3 = -104
    L3E4 = -108
    # 同一周期连续4个峰谷间的关系：P==峰，V==谷，G==大于，L==小于，E==等于
    PGPVGP = 1  # long trend
    PGPVGV = 2
    PGPVEV = 3
    PGPVLV = 4  # short trend
    PEPVGV = 5
    PEPVEV = 6
    PEPVLV = 7  # short trend
    PLPVGV = 8
    PLPVEV = 9
    PLPVLV = 10  # short trend
    VLVPLV = 11
    VLVPLP = 12
    VLVPEP = 13
    VLVPGP = 14  # long trend
    VEVPLP = 15
    VEVPEP = 16
    VEVPGP = 17  # long trend
    VGVPLP = 18
    VGVPEP = 19
    VGVPGP = 20  # long trend

    PPVGV = [1, 2, 5, 8]
    PPVEV = [3, 6, 9]
    PPVLV = [4, 7, 10]
    VVPLP = [11, 12, 15, 18]
    VVPEP = [13, 16, 19]
    VVPGP = [14, 17, 20]
    PLPVV = [8, 9, 10]
    PEPVV = [5, 6, 7]
    PGPVV = [1, 2, 3, 4]
    VLVPP = [11, 12, 13, 14]
    VEVPP = [15, 16, 17]
    VGVPP = [18, 19, 20]
    # PV4 的转移矩阵
    #    PV4TM = [{PPVGV: [18, 19, 20], [3, 6, 9]: [15, 16, 17], [4, 7, 10]: [11, 12, 13, 14]}]
    #  依据 PV4 进行操作的规则：
    #  若 PV4 < 11 开空要设置止损价：前峰
    #  若 PV4 > 10 开多要设置止损价：前谷
    PV4 = dict(PGPVGP=1,  # long trend
               PGPVGV=2,
               PGPVEV=3,
               PGPVLV=4,  # short trend
               PEPVGV=5,
               PEPVEV=6,
               PEPVLV=7,  # short trend
               PLPVGV=8,
               PLPVEV=9,
               PLPVLV=10,  # short trend
               VLVPLV=11,
               VLVPLP=12,
               VLVPEP=13,
               VLVPGP=14,  # long trend
               VEVPLP=15,
               VEVPEP=16,
               VEVPGP=17,  # long trend
               VGVPLP=18,
               VGVPEP=19,
               VGVPGP=20,  # long trend
               )
    LONG_COND_PV4 = [1, 2, 3, 5, 6, 8, 9, 14, 17, 20]  # 适宜开多，但要根据统计数据来操作
    SHORT_COND_PV4 = [11, 12, 13, 15, 16, 18, 19, 4, 7, 10]
    LONG_STOP_COND_PV4 = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]  # 当开多时的 PV4 在此列表中时要设置止损
    SHORT_STOP_COND_PV4 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]  # 当开空时的 PV4 在此列表中时要设置止损
    # {PtVt}t 是已经确定的峰谷序列，P0V0称为降段，记为D0，V0P1称为升段，记为U0，即峰前谷后为降段，谷前峰后为升段
    # 前后两段形成一个区间，根据区间内的D0和U0的长度关系，将区间分为：
    # 升区：D0<U0 and D0>U0*2./3, 记为UA
    # 降区:D0>U0 and U0>D0*2./3, 记为DA
    # 盘区: 非升区 and 非降区， 记为SA
    # 12 小类：Ui, i=1,2,3,4,5,6, U*(i-1)/6<D<=U*i/6
    #         Di, i=1,2,3,4,5,6, D*(i-1)/6<U<=D*i/6
    U1 = 1
    U2 = 2
    U3 = 3
    U4 = 4
    U5 = 5
    U6 = 6
    D1 = -1
    D2 = -2
    D3 = -3
    D4 = -4
    D5 = -5
    D6 = -6
    UDR = dict(U1=1,
               U2=2,
               U3=3,
               U4=4,
               U5=5,
               U6=6,
               D1=-1,
               D2=-2,
               D3=-3,
               D4=-4,
               D5=-5,
               D6=-6,
               UN=0,
               )
    UA = 1  # 升区
    DA = -1  # 降区
    SA = 0  # 盘区
    UDA = dict(
        UA=1,
        DA=-1,
        SA=0,
    )
    UN = 0

    @classmethod
    def clc_udra(cls, d: float, u: float):
        """根据已知的升区长度u, 和降区长度d, 计算其关系"""
        udsa = 0  # self.SA， 三大类
        udr = 0  # self.UN # 12 小类
        if d > u:
            udsa = cls.DA
            if u <= 1. / 6 * d:
                udr = cls.D1
            elif u <= 1. / 3 * d:
                udr = cls.D2
            elif u <= 1. / 2 * d:
                udr = cls.D3
            elif u <= 2. / 3 * d:
                udr = cls.D4
            elif u <= 5. / 6 * d:
                udr = cls.D5
            else:
                udr = cls.D6
                udsa = cls.SA
        elif d < u:
            udsa = cls.UA
            if d <= 1. / 6 * u:
                udr = cls.U1
            elif d <= 1. / 3 * u:
                udr = cls.U2
            elif d <= 1. / 2 * u:
                udr = cls.U3
            elif d <= 2. / 3 * u:
                udr = cls.U4
            elif d <= 5. / 6 * u:
                udr = cls.U5
            else:
                udr = cls.U6
                udsa = cls.SA
        else:  # d==u
            udsa = cls.SA

        return udsa, udr


def clc_pv_windows(p0, v0, h0, l0, pp0, pv0, ph0, pl0, cur_pv_flag, windows=3):
    """根据窗口长度，计算峰谷
    :param p0: 已经确认的峰或高点
    :param v0: 已经确认的谷或低点
    :param h0: 新的高
    :param l0: 新的低
    :param pp0, pv0, ph0, pl0: p0,v0,h0,l0 对应的位置
    :param cur_pv_flag: 当前的峰谷标志：0＝＝未确认，1＝＝峰，－1＝＝谷，2＝＝高先低后，－2＝＝低先高后
    :param windows: 峰谷间的最小间隔周期
    :return: p,v,pp,pv,pv_flag
    """
    if cur_pv_flag == PVType.P:  # 当前是峰,要确认谷
        if ph0 - pv0 < windows:
            if l0 < v0:  # 创新低
                return h0, l0, ph0, pl0, cur_pv_flag
            elif h0 > p0:
                return h0, v0, ph0, pv0, cur_pv_flag
            else:
                return p0, v0, pp0, pv0, cur_pv_flag
        else:
            if l0 < v0:  # 创新低
                return h0, l0, ph0, pl0, cur_pv_flag
            elif h0 > p0:  # 谷被确认
                return h0, v0, ph0, pv0, PVType.V
            else:
                return p0, v0, pp0, pv0, cur_pv_flag
    elif cur_pv_flag == PVType.V:  # 当前是谷,要确认峰
        if ph0 - pv0 < windows:
            if h0 > p0:  # 创新高
                return h0, l0, ph0, pl0, cur_pv_flag
            elif l0 < v0:
                return p0, l0, pp0, pl0, cur_pv_flag
            else:
                return p0, v0, pp0, pv0, cur_pv_flag
        else:
            if h0 > p0:  # 创新高
                return h0, l0, ph0, pl0, cur_pv_flag
            elif l0 > v0:  # 峰被确认
                return p0, l0, pp0, pl0, PVType.P
            else:
                return p0, v0, pp0, pv0, cur_pv_flag
    elif cur_pv_flag == PVType.LL:  # 最新是低
        if h0 > p0:  # 创新高
            if ph0 - pv0 < windows:
                return h0, v0, ph0, pv0, PVType.HH
            else:  # 谷被确认
                return h0, v0, ph0, pv0, PVType.V
        elif l0 < v0:
            if pl0 - pp0 < windows:
                return p0, l0, pp0, pl0, cur_pv_flag
            else:  # 峰被确认
                return p0, l0, pp0, pl0, PVType.P
        else:
            return p0, v0, pp0, pv0, cur_pv_flag
    elif cur_pv_flag == PVType.HH:
        if l0 < v0:  # 创新低
            if pl0 - pp0 < windows:
                return p0, l0, pp0, pl0, PVType.LL
            else:  # 峰被确认
                return p0, l0, pp0, pl0, PVType.P
        elif h0 > p0:
            if ph0 - pv0 < windows:
                return h0, v0, ph0, pv0, cur_pv_flag
            else:  # 谷被确认
                return h0, v0, ph0, pv0, PVType.V
        else:
            return p0, v0, pp0, pv0, cur_pv_flag
    else:
        if h0 > p0:
            return h0, v0, ph0, pv0, PVType.HH
        else:
            return p0, l0, pp0, pl0, PVType.LL


def pv_windows(h, l, windows=3):
    """根据给定的高、低价序列　h,l 和最小周期窗口　windows,计算峰和谷
    :param h: np.array
    :param l: np.array
    :param windows: 最小的窗口周期数
    :return : np.array(pv_flag,pv,pv_position，ok_price,ok_position)
    """
    i = 0
    p0 = h[i]
    v0 = l[i]
    pp0 = i
    vp0 = i
    cur_pv_flag = PVType.UN
    pv = []
    for i in range(1, len(h)):
        rslt = clc_pv_windows(p0, v0, h[i], l[i], pp0, vp0, i, i, cur_pv_flag, windows)
        p, v, pp, vp, pv_flag = rslt
        if cur_pv_flag != pv_flag:
            if pv_flag == PVType.P:
                pv.append([pv_flag, p, pp, l[i], i])
                p0, v0, pp0, vp0 = h[i], v, i, vp
            elif pv_flag == PVType.V:
                pv.append([pv_flag, v, vp, h[i], i])
                p0, v0, pp0, vp0 = p, l[i], pp, i
            elif pv_flag == PVType.HH:
                pv.append([pv_flag, p, pp, l[i], i])
                p0, v0, pp0, vp0 = p, v, pp, vp
            elif pv_flag == PVType.LL:
                pv.append([pv_flag, v, vp, h[i], i])
                p0, v0, pp0, vp0 = p, v, pp, vp
            cur_pv_flag = pv_flag
    return pv


def clc_pv_windows_tick(p0, v0, last_price, pp0, pv0, pl0, cur_pv_flag, windows=3):
    """根据窗口长度，计算峰谷
    :param p0: 已经确认的峰或高点
    :param v0: 已经确认的谷或低点
    :param last_price: 新的价格
    :param pp0,pv0,pl0: p0,v0,last_price 对应的位置
    :param cur_pv_flag: 当前的峰谷标志：0＝＝未确认，1＝＝峰，－1＝＝谷，2＝＝高先低后，－2＝＝低先高后
    :param windows: 峰谷间的最小间隔周期
    :return: p,v,pp,pv,pv_flag
    """
    if cur_pv_flag == PVType.P:  # 当前是峰,要确认谷
        if pl0 - pv0 < windows:
            if last_price < v0:  # 创新低                
                return last_price, last_price, pl0, pl0, cur_pv_flag
            elif last_price > p0:
                return last_price, v0, pl0, pv0, cur_pv_flag
            else:
                return p0, v0, pp0, pv0, cur_pv_flag
        else:
            if last_price < v0:  # 创新低
                return last_price, last_price, pl0, pl0, cur_pv_flag
            elif last_price > p0:  # 谷被确认
                return last_price, v0, pl0, pv0, PVType.V
            else:
                return p0, v0, pp0, pv0, cur_pv_flag
    elif cur_pv_flag == PVType.V:  # 当前是谷,要确认峰
        if pl0 - pv0 < windows:
            if last_price > p0:  # 创新高
                return last_price, last_price, pl0, pl0, cur_pv_flag
            elif last_price < v0:
                return p0, last_price, pp0, pl0, cur_pv_flag
            else:
                return p0, v0, pp0, pv0, cur_pv_flag
        else:
            if last_price > p0:  # 创新高
                return last_price, last_price, pl0, pl0, cur_pv_flag
            elif last_price > v0:  # 峰被确认
                return p0, last_price, pp0, pl0, PVType.P
            else:
                return p0, v0, pp0, pv0, cur_pv_flag
    elif cur_pv_flag == PVType.LL:  # 最新是低
        if last_price > p0:  # 创新高
            if pl0 - pv0 < windows:
                return last_price, v0, pl0, pv0, PVType.HH
            else:  # 谷被确认
                return last_price, v0, pl0, pv0, PVType.V
        elif last_price < v0:
            if pl0 - pp0 < windows:
                return p0, last_price, pp0, pl0, cur_pv_flag
            else:  # 峰被确认
                return p0, last_price, pp0, pl0, PVType.P
        else:
            return p0, v0, pp0, pv0, cur_pv_flag
    elif cur_pv_flag == PVType.HH:
        if last_price < v0:  # 创新低
            if pl0 - pp0 < windows:
                return p0, last_price, pp0, pl0, PVType.LL
            else:  # 峰被确认
                return p0, last_price, pp0, pl0, PVType.P
        elif last_price > p0:
            if pl0 - pv0 < windows:
                return last_price, v0, pl0, pv0, cur_pv_flag
            else:  # 谷被确认
                return last_price, v0, pl0, pv0, PVType.V
        else:
            return p0, v0, pp0, pv0, cur_pv_flag
    else:
        if last_price > p0:
            return last_price, v0, pl0, pv0, PVType.HH
        else:
            return p0, last_price, pp0, pl0, PVType.LL


def pv_windows_tick(last_prices, windows=120):
    """根据给定的价格序列 last_prices 和最小周期窗口　windows,计算峰和谷
    :param last_prices: np.array
    :param windows: 最小的窗口周期数
    :return : np.array(pv_flag,pv,pv_position，ok_price,ok_position)
    """
    i = 0
    p0 = last_prices[i]
    v0 = last_prices[i]
    pp0 = i
    vp0 = i
    cur_pv_flag = PVType.UN
    pv = []
    for i in range(1, len(last_prices)):
        rslt = clc_pv_windows_tick(p0, v0, last_prices[i], pp0, vp0, i, cur_pv_flag, windows)
        p, v, pp, vp, pv_flag = rslt
        if cur_pv_flag != pv_flag:
            if pv_flag == PVType.P:
                pv.append([pv_flag, p, pp, last_prices[i], i])
                p0, v0, pp0, vp0 = last_prices[i], v, i, vp
            elif pv_flag == PVType.V:
                pv.append([pv_flag, v, vp, last_prices[i], i])
                p0, v0, pp0, vp0 = p, last_prices[i], pp, i
            elif pv_flag == PVType.HH:
                pv.append([pv_flag, p, pp, last_prices[i], i])
                p0, v0, pp0, vp0 = p, v, pp, vp
            elif pv_flag == PVType.LL:
                pv.append([pv_flag, v, vp, last_prices[i], i])
                p0, v0, pp0, vp0 = p, v, pp, vp
            cur_pv_flag = pv_flag
    return pv


def clc_pv_tick(hh0, ll0, last_price, p_hh0, p_ll0, pl0, cur_flag, xprice=0, xposi=0, rng=5, wnds=360):
    """根据窗口长度windows和相对距离rng，计算峰谷
    :param hh0: 当前的局部高点
    :param ll0: 当前的局部低点
    :param last_price: 新的价格
    :param p_hh0, p_ll0, pl0: hh0, ll0, last_price 对应的位置
    :param cur_flag: 当前的峰谷标志：见 PVType 的定义，0＝＝未确认
    :param xprice: 当cur_flag in [PVType::[WPO,WVO,PPO,PVO]] 时的 价格，
    :param xposi: 当cur_flag in [PVType::[WPO,WVO,PPO,PVO]] 时的 位置
    :param rng: (p-v)/(p+v)/2*1000 的最小要求，5就是千分之五
    :param wnds: 峰谷间的最小间隔周期,对于 tick 来说, 360相当于3分钟
    :return:hh,ll,p_hh,p_ll, pv_flag, xprice, xposition
    """

    if cur_flag == PVType.QWPO:  # 当前是周期峰,要确认周期谷
        if last_price < ll0:  # new low
            if (xprice - last_price) * 2 / (xprice + last_price) * 1000 >= rng:  # 相对距离满足峰的定义
                return last_price, last_price, pl0, pl0, PVType.PO, xprice, xposi
            else:  # 准周期峰转变为周期峰
                return last_price, last_price, pl0, pl0, PVType.WPO, xprice, xposi

        elif last_price > hh0:  # new high
            if (last_price - ll0) * 2 / (ll0 + last_price) * 1000 >= rng:  # 相对距离满足谷的定义
                if pl0 - p_ll0 < wnds:
                    return last_price, last_price, pl0, pl0, PVType.PVO, ll0, p_ll0
                else:
                    return last_price, last_price, pl0, pl0, PVType.VO, ll0, p_ll0
            else:
                if pl0 - p_ll0 < wnds:
                    return last_price, ll0, pl0, p_ll0, cur_flag, xprice, xposi
                else:  # periods valley
                    return last_price, last_price, pl0, pl0, PVType.WVO, ll0, p_ll0
        else:
            if pl0 - p_ll0 < wnds:
                return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
            else:
                return last_price, last_price, pl0, pl0, PVType.QWVO, ll0, p_ll0
    elif cur_flag == PVType.WPO:  # 当前是周期峰,要确认周期谷
        if last_price < ll0:  # new low
            if (xprice - last_price) * 2 / (xprice + last_price) * 1000 >= rng:  # 相对距离满足峰的定义
                return last_price, last_price, pl0, pl0, PVType.PO, xprice, xposi
            else:
                return last_price, last_price, pl0, pl0, cur_flag, xprice, xposi

        elif last_price > hh0:  # new high
            if (last_price - ll0) * 2 / (ll0 + last_price) * 1000 >= rng:  # 相对距离满足谷的定义
                if pl0 - p_ll0 < wnds:
                    return last_price, last_price, pl0, pl0, PVType.PVO, ll0, p_ll0
                else:
                    return last_price, last_price, pl0, pl0, PVType.VO, ll0, p_ll0
            else:
                if pl0 - p_ll0 < wnds:
                    return last_price, ll0, pl0, p_ll0, cur_flag, xprice, xposi
                else:  # periods valley
                    return last_price, last_price, pl0, pl0, PVType.WVO, ll0, p_ll0
        else:
            if pl0 - p_ll0 < wnds:
                return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
            else:
                return last_price, last_price, pl0, pl0, PVType.QWVO, ll0, p_ll0
    elif cur_flag == PVType.QWVO:  # 当前是周期峰,要确认周期谷
        if last_price > hh0:  # new low
            if (-xprice + last_price) * 2 / (xprice + last_price) * 1000 >= rng:  # 相对距离满足峰的定义
                return last_price, last_price, pl0, pl0, PVType.VO, xprice, xposi
            else:  # 准周期转变为周期
                return last_price, last_price, pl0, pl0, PVType.WVO, xprice, xposi

        elif last_price < ll0:  # new high
            if (hh0 - last_price) * 2 / (hh0 + last_price) * 1000 >= rng:  # 相对距离满足谷的定义
                if pl0 - p_hh0 < wnds:
                    return last_price, last_price, pl0, pl0, PVType.PPO, hh0, p_hh0
                else:
                    return last_price, last_price, pl0, pl0, PVType.PO, hh0, p_hh0
            else:
                if pl0 - p_hh0 < wnds:
                    return hh0, last_price, p_hh0, pl0, cur_flag, xprice, xposi
                else:  # periods valley
                    return last_price, last_price, pl0, pl0, PVType.WPO, hh0, p_hh0
        else:
            if pl0 - p_hh0 < wnds:
                return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
            else:
                return last_price, last_price, pl0, pl0, PVType.QWPO, hh0, p_hh0
    elif cur_flag == PVType.WVO:  # 当前是周期谷,要确认周期峰
        if last_price > hh0:  # new high
            if (-xprice + last_price) * 2 / (xprice + last_price) * 1000 >= rng:  # 相对距离满足峰的定义
                return last_price, last_price, pl0, pl0, PVType.VO, xprice, xposi
            else:
                return last_price, last_price, pl0, pl0, cur_flag, xprice, xposi

        elif last_price < ll0:  # new low
            if (hh0 - last_price) * 2 / (hh0 + last_price) * 1000 >= rng:  # 相对距离满足谷的定义
                if pl0 - p_hh0 < wnds:
                    return last_price, last_price, pl0, pl0, PVType.PPO, hh0, p_hh0
                else:
                    return last_price, last_price, pl0, pl0, PVType.PO, hh0, p_hh0
            else:
                if pl0 - p_ll0 < wnds:
                    return last_price, ll0, pl0, p_ll0, cur_flag, xprice, xposi
                else:  # periods valley
                    return last_price, last_price, pl0, pl0, PVType.WPO, hh0, p_hh0
        else:
            if pl0 - p_hh0 < wnds:
                return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
            else:
                return last_price, last_price, pl0, pl0, PVType.QWPO, hh0, p_hh0

    elif cur_flag == PVType.PPO:  # 当前是价格峰,要确认谷
        if pl0 - xposi < wnds:
            if last_price < ll0:  # 创新低
                return last_price, last_price, pl0, pl0, cur_flag, xprice, xposi
            elif last_price > hh0:
                if (last_price - ll0) * 2 / (ll0 + last_price) * 1000 >= rng:  # 相对距离满足峰的定义
                    return last_price, last_price, pl0, pl0, PVType.PVO, ll0, p_ll0
                else:
                    return last_price, ll0, pl0, p_ll0, cur_flag, xprice, xposi
            else:
                return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
        else:
            # print(hh0, last_price, ll0, pl0, p_ll0, cur_flag, xprice, xposition)
            if last_price < ll0:  # 创新低
                return last_price, last_price, pl0, pl0, PVType.PO, xprice, xposi
            elif last_price > hh0:  # 谷被确认
                if (last_price - ll0) * 2 / (ll0 + last_price) * 1000 >= rng:  # 相对距离满足谷的定义
                    if pl0 - p_ll0 < wnds:
                        return last_price, last_price, pl0, pl0, PVType.PVO, ll0, p_ll0
                    else:
                        return last_price, last_price, pl0, pl0, PVType.VO, ll0, p_ll0
                else:
                    if pl0 - p_ll0 < wnds:
                        return last_price, ll0, pl0, p_ll0, cur_flag, xprice, xposi
                    else:  # periods valley
                        return last_price, last_price, pl0, pl0, PVType.WVO, ll0, p_ll0
            else:
                return hh0, ll0, p_hh0, p_ll0, PVType.QPO, xprice, xposi
    elif cur_flag == PVType.QPO:  # 当前是价格峰,要确认谷
        # print(hh0, last_price, ll0, pl0, p_ll0, cur_flag, xprice, xposition)
        if last_price < ll0:  # 创新低
            return last_price, last_price, pl0, pl0, PVType.PO, xprice, xposi
        elif last_price > hh0:
            if (last_price - ll0) * 2 / (ll0 + last_price) * 1000 >= rng:  # 相对距离满足谷的定义
                if pl0 - p_ll0 < wnds:
                    return last_price, last_price, pl0, pl0, PVType.PVO, ll0, p_ll0
                else:
                    return last_price, last_price, pl0, pl0, PVType.VO, ll0, p_ll0
            else:
                if pl0 - p_ll0 < wnds:
                    return last_price, ll0, pl0, p_ll0, cur_flag, xprice, xposi
                else:  # periods valley
                    return last_price, last_price, pl0, pl0, PVType.WVO, ll0, p_ll0
        else:
            if pl0 - p_ll0 < wnds:
                return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
            else:
                return last_price, last_price, pl0, pl0, PVType.QWVO, ll0, p_ll0
    elif cur_flag == PVType.PVO:  # 当前是价格谷,要确认
        if pl0 - xposi < wnds:
            if last_price > hh0:  # 创新高
                return last_price, last_price, pl0, pl0, cur_flag, xprice, xposi
            elif last_price < ll0:
                if (hh0 - last_price) * 2 / (hh0 + last_price) * 1000 >= rng:  # 相对距离满足峰的定义
                    return last_price, last_price, pl0, pl0, PVType.PPO, hh0, p_hh0
                else:
                    return hh0, last_price, p_hh0, pl0, cur_flag, xprice, xposi
            else:
                return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
        else:
            if last_price > hh0:  # 创新高
                return last_price, last_price, pl0, pl0, PVType.VO, xprice, xposi
            elif last_price < ll0:  # 谷被确认
                if (hh0 - last_price) * 2 / (hh0 + last_price) * 1000 >= rng:  # 相对距离满足谷的定义
                    if pl0 - p_ll0 < wnds:
                        return last_price, last_price, pl0, pl0, PVType.PPO, hh0, p_hh0
                    else:
                        return last_price, last_price, pl0, pl0, PVType.PO, hh0, p_hh0
                else:
                    if pl0 - p_hh0 < wnds:
                        return hh0, last_price, p_hh0, pl0, cur_flag, xprice, xposi
                    else:  # periods valley
                        return last_price, last_price, pl0, pl0, PVType.WPO, hh0, p_hh0
            else:
                return hh0, ll0, p_hh0, p_ll0, PVType.QVO, xprice, xposi
    elif cur_flag == PVType.QVO:  # 当前是价格峰,要确认谷
        # print(hh0, last_price, ll0, pl0, p_ll0, cur_flag, xprice, xposition)
        if last_price > hh0:  # 创新高
            return last_price, last_price, pl0, pl0, PVType.VO, xprice, xposi
        elif last_price < ll0:  # 创新低
            if (hh0 - last_price) * 2 / (hh0 + last_price) * 1000 >= rng:  # 相对距离满足谷的定义
                if pl0 - p_hh0 < wnds:
                    return last_price, last_price, pl0, pl0, PVType.PPO, hh0, p_hh0
                else:
                    return last_price, last_price, pl0, pl0, PVType.PO, hh0, p_hh0
            else:
                if pl0 - p_hh0 < wnds:
                    return hh0, last_price, p_hh0, pl0, cur_flag, xprice, xposi
                else:  # periods peak
                    return last_price, last_price, pl0, pl0, PVType.WPO, hh0, p_hh0
        else:
            if pl0 - p_hh0 < wnds:
                return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
            else:
                return last_price, last_price, pl0, pl0, PVType.QWPO, hh0, p_hh0

    elif cur_flag == PVType.PO:  # 当前是峰,要确认谷
        if last_price < ll0:  # 创新低
            return last_price, last_price, pl0, pl0, cur_flag, xprice, xposi
        elif last_price > hh0:
            if (last_price - ll0) * 2 / (ll0 + last_price) * 1000 >= rng:  # 相对距离满足峰的定义
                if pl0 - p_ll0 < wnds:
                    return last_price, last_price, pl0, pl0, PVType.PVO, ll0, p_ll0
                else:
                    return last_price, last_price, pl0, pl0, PVType.VO, ll0, p_ll0
            else:
                if pl0 - p_ll0 < wnds:
                    return last_price, ll0, pl0, p_ll0, cur_flag, xprice, xposi
                else:  # periods valley
                    return last_price, last_price, pl0, pl0, PVType.WVO, ll0, p_ll0
        else:
            if pl0 - p_ll0 < wnds:
                return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
            else:
                return last_price, last_price, pl0, pl0, PVType.QWVO, ll0, p_ll0

    elif cur_flag == PVType.VO:  # 当前是谷,要确认峰
        if last_price > hh0:  # 创新高
            return last_price, last_price, pl0, pl0, cur_flag, xprice, xposi
        elif last_price < ll0:
            if (hh0 - last_price) * 2 / (hh0 + last_price) * 1000 >= rng:  # 相对距离满足谷的定义
                if pl0 - p_hh0 < wnds:
                    return last_price, last_price, pl0, pl0, PVType.PPO, hh0, p_hh0
                else:
                    return last_price, last_price, pl0, pl0, PVType.PO, hh0, p_hh0
            else:
                if pl0 - p_hh0 < wnds:
                    return hh0, last_price, p_hh0, pl0, cur_flag, xprice, xposi
                else:  # periods peak
                    return last_price, last_price, pl0, pl0, PVType.WPO, hh0, p_hh0
        else:
            if pl0 - p_hh0 < wnds:
                return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
            else:
                return last_price, last_price, pl0, pl0, PVType.QWPO, hh0, p_hh0

    elif cur_flag == PVType.LLL:  # 最新是低
        if last_price < ll0:  # 可能的结果：价格峰被确认、峰被确认、周期峰被确认、不变
            if pl0 - p_hh0 < wnds:
                if (hh0 - last_price) * 2 * 1000 / (last_price + hh0) > rng:  # 价格峰被确认
                    return hh0, ll0, pl0, p_ll0, PVType.PPO, hh0, p_hh0
                return hh0, last_price, p_hh0, pl0, cur_flag, xprice, xposi
            elif (hh0 - last_price) * 2 * 1000 / (last_price + hh0) > rng:  # 峰被确认
                return last_price, ll0, pl0, p_ll0, PVType.PO, hh0, p_hh0
            else:  # 周期峰被确认
                return hh0, last_price, p_hh0, pl0, PVType.WPO, hh0, p_hh0
        elif last_price >= hh0:  # 创新高
            if pl0 - p_ll0 < wnds:
                if (last_price - ll0) * 2 * 1000 / (last_price + ll0) > rng:  # 距离谷被确认
                    return last_price, ll0, pl0, p_ll0, PVType.PVO, ll0, p_ll0
                else:
                    return last_price, ll0, pl0, p_ll0, PVType.HHH, last_price, pl0
            elif (last_price - ll0) * 2 * 1000 / (last_price + ll0) > rng:  # 谷被确认
                return last_price, ll0, pl0, p_ll0, PVType.VO, ll0, p_ll0
            else:  # 周期谷被确认
                return last_price, ll0, pl0, p_ll0, PVType.WVO, ll0, p_ll0

        elif pl0 - p_ll0 >= wnds:  # 周期谷被确认
            return last_price, ll0, pl0, p_ll0, PVType.WVO, ll0, p_ll0
        else:
            return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
    elif cur_flag == PVType.HHH:
        if last_price < ll0:  # 创新低
            if pl0 - p_hh0 < wnds:
                if (xprice - last_price) * 2 * 1000 / (last_price + xprice) > rng:  # 价格峰被确认
                    return last_price, ll0, pl0, p_ll0, PVType.PPO, xprice, xposi
                elif ll0 > xprice:
                    return hh0, last_price, p_hh0, pl0, PVType.LLL, last_price, pl0
                else:
                    return hh0, ll0, p_hh0, p_ll0, cur_flag, last_price, pl0
            elif (hh0 - last_price) * 2 * 1000 / (last_price + hh0) > rng:  # 峰被确认
                return last_price, ll0, pl0, p_ll0, PVType.PO, hh0, p_hh0
            else:  # 周期峰被确认
                if ll0 < last_price:
                    ll0 = last_price,
                    p_ll0 = pl0
                return hh0, ll0, p_hh0, p_ll0, PVType.WPO, hh0, p_hh0
        elif last_price > hh0:  # 创新高
            if pl0 - p_ll0 < wnds:
                if (last_price - ll0) * 2 * 1000 / (last_price + ll0) > rng:  # 距离谷被确认
                    return last_price, last_price, pl0, pl0, PVType.PVO, ll0, p_ll0
                return last_price, ll0, pl0, p_ll0, cur_flag, last_price, pl0
            elif (last_price - ll0) * 2 * 1000 / (last_price + ll0) > rng:  # 谷被确认
                return last_price, last_price, pl0, pl0, PVType.VO, ll0, p_ll0
            else:  # 周期谷被确认
                return last_price, last_price, pl0, pl0, PVType.WVO, ll0, p_ll0
        elif pl0 - p_ll0 >= wnds:  # 准周期谷被确认
            return hh0, ll0, p_hh0, p_ll0, PVType.QWVO, ll0, p_ll0
        else:
            return hh0, ll0, p_hh0, p_ll0, cur_flag, xprice, xposi
    else:  # cur_pv_flag==UN
        if last_price >= hh0:
            return last_price, ll0, pl0, p_ll0, PVType.HHH, last_price, pl0
        else:
            return hh0, last_price, p_hh0, pl0, PVType.LLL, last_price, pl0


def pv_tick(last_prices, rng=5, wnds=360, out_type='df'):
    """根据给定的价格序列 last_prices 和最小周期窗口　windows,计算峰和谷
    :param last_prices: np.array
    :param rng:  (p-v)/(p+v)/2*1000 的最小要求，5就是千分之五
    :param wnds: 最小的窗口周期数
    :return : np.array(pv_flag,pv,pv_position，ok_price,ok_position)
    """
    i = 0
    p0 = last_prices[i]
    v0 = last_prices[i]
    h0 = p0
    l0 = p0
    pp0 = i
    vp0 = i
    xprice, xposi = 0, 0
    cur_pv_flag = PVType.UN
    pv = []
    # 找出峰（谷）的各类确认点（2，3，12，13，102）
    for i in range(1, len(last_prices)):
        rslt = clc_pv_tick(p0, v0, last_prices[i], pp0, vp0, i, cur_pv_flag, xprice, xposi, rng, wnds)

        p, v, pp, vp, pv_flag, xprice, xposi = rslt
        # print('结果：', rslt)
        if cur_pv_flag != pv_flag:
            pv.append(
                [pv_flag, xprice, xposi, last_prices[i], i, p, v, pp, vp])
            # if pv_flag in [PVType.PO, PVType.QPO, PVType.WPO, PVType.QWPO, PVType.PPO]:
            #     pv.append([pv_flag, xprice, xposi, last_prices[i], i, p, v, pp, vp])
            # elif pv_flag in [PVType.VO, PVType.QVO, PVType.WVO, PVType.QWVO, PVType.PVO]:
            #     pv.append(
            #         [pv_flag, xprice, xposi, last_prices[i], i, p, v, pp, vp])

            # elif pv_flag == PVType.HHH:
            #     pv.append([pv_flag, p, pp, last_prices[i], i, p, v, pp, vp])
            # elif pv_flag == PVType.LLL:
            #     pv.append([pv_flag, v, vp, last_prices[i], i, p, v, pp, vp])

        p0, v0, pp0, vp0 = p, v, pp, vp
        cur_pv_flag = pv_flag

    column_name = ['flag', 'price', 'posi', 'ok_p', 'ok_posi', 'hh', 'll', 'hhp', 'llp']
    s = [pv[i] for i in range(len(pv)) if abs(pv[i][0]) != PVType.HHH]
    df = pd.DataFrame(data=s, columns=column_name)
    # for i in range(1, 5):
    #     sf = f'f_{i}'
    #     df[sf] = df.flag.shift(i)
    #     sp = f'price_{i}'
    #     df[sp] = df.price.shift(i)
    #     spp = f'posi_{i}'
    #     df[spp] = df.posi.shift(i)
    #     sop = f'ok_price_{i}'
    #     df[sop] = df.ok_p.shift(i)
    #     dop = f'diff_ok_p{i}'
    #     df[dop] = -df.ok_p.diff(-i)
    #     soposi = f'ok_posi_{i}'
    #     df[soposi] = df.ok_posi.shift(i)
    #
    # df['f1'] = df.flag.shift(-1)
    # df['price1'] = df.price.shift(-1)
    # df['ok_price1'] = df.ok_p.shift(-1)
    # # 删除连续同符号的 flag, 保留最后一个，定义为 real_flag， 只分峰＝1，谷＝－1，且峰谷相间
    # df['flag_m_p'] = 0
    # df.loc[df.flag < 0, 'flag_m_p'] = -1
    # df.loc[df.flag > 0, 'flag_m_p'] = 1
    # x = df['flag_m_p'][::-1].rolling(window=2, min_periods=1).sum()[::-1] + df['flag_m_p']
    # df['real_flag'] = 0
    # df.loc[x == 1, 'real_flag'] = 1
    # df.loc[x == -1, 'real_flag'] = -1
    #
    # real_flag = df[df['real_flag'] != 0][['real_flag', 'price', 'ok_p', 'flag']]
    # real_flag['ud'] = abs(real_flag['price'].diff(1))  # 峰（谷）的高（深）度
    # # 计算升段、降段间的关系，并分类
    # udr = [0, 0]  # 分类：1,2,...,6,-1,-2,...,-6
    # uda = [0, 0]  # 关系:-1,0,1
    # d0 = 0
    # u0 = 0
    # if real_flag.iloc[0]['real_flag'] == 1:
    #     d0 = real_flag.iloc[1]['ud']
    # else:
    #     u0 = real_flag.iloc[1]['ud']
    #
    # for i in range(2, len(real_flag)):
    #     if u0 <= 0:
    #         u0 = real_flag.iloc[i]['ud']
    #         a0, r0 = HLRelationship.clc_udra(d0, u0)
    #         udr.append(r0)
    #         uda.append(a0)
    #         d0 = 0
    #     else:
    #         d0 = real_flag.iloc[i]['ud']
    #         a0, r0 = HLRelationship.clc_udra(d0, u0)
    #         udr.append(r0)
    #         uda.append(a0)
    #         u0 = 0
    # real_flag['uda'] = uda
    # real_flag['udr'] = udr
    # df['uda'] = real_flag['uda']
    # df['udr'] = real_flag['udr']
    #
    # # 对 连续4个PV进行分类
    # pv4 = [0, 0, 0]
    # dl = len(real_flag)
    # if dl >= 4:
    #     for i in range(3, dl):
    #         zc = clc_zc(real_flag.iloc[i - 3]['price'], real_flag.iloc[i - 2]['price'], real_flag.iloc[i - 1]['price'],
    #                     real_flag.iloc[i]['price'])
    #         pv4.append(zc)
    # real_flag['pv4'] = pv4
    #
    # df['pv4'] = real_flag['pv4']
    # rdf, prob = clc_prob(df)

    return df


def classifier_stat(df: pd.DataFrame, g_fields=None, cri_field='', out_flat_index=True):
    """分类器统计分析
    :param df: 已经分好类的数据
    :param g_fields: 分类列名称
    :param cri_field: 评价分类优劣的列名称, 可能的值：diff_ok_p1， diff_ok_p2， diff_ok_p3， diff_ok_p4
    :param out_flat_index: 1::单一索引，0::pd.MultiIndex
    """
    if not g_fields:
        g_fields = ['flag', 'pre_pv4']
    if not cri_field:
        cri_field = 'diff_ok_p1'
    #
    df['g_2'] = 0
    df.loc[df[cri_field] > 0, 'g_2'] = 1
    df.loc[df[cri_field] < 0, 'g_2'] = -1

    x = df.groupby(g_fields)[cri_field]
    # 按做多进行收益计算，此时假设不能正确区分多空方向，实际操作中按收益符号进行
    ts = x.sum()  # 收益
    tc = x.count()  # 次数
    tm = x.median()  # 中位数

    # 将 评价分组加入分类
    x = df.groupby(g_fields + ['g_2'])[cri_field]
    # 按做多进行收益计算，此时假设能正确区分多空方向，实际操作中按绝对值大的方向进行
    xs1 = x.sum().unstack(-1).fillna(0).rename(columns={1: 'long', 0: 'money', -1: 'short'})
    # 正负收益次数统计
    xc1 = x.count().unstack(-1).fillna(0).rename(columns={1: 'l_c', 0: 'm_c', -1: 's_c'})
    # 正负收益时的中位数
    xm1 = x.median().unstack(-1).fillna(0).rename(columns={1: 'l_m', 0: 'm_m', -1: 's_m'})
    win_rate = xc1.truediv(tc, axis='index').rename(
        columns={'l_c': 'l_w_r', 'm_c': 'm_w_r', 's_c': 's_w_r'})  # 以次数计算的胜率

    stat = xs1.join(xm1).join(win_rate).join(xc1)
    stat['t_prof'] = ts
    stat['t_count'] = tc
    stat['right_prof'] = stat.long - stat.short
    stat['prof_rate'] = (stat.long + stat.short) / stat.right_prof

    if out_flat_index:
        stat = stat.reset_index(drop=True)

    return stat


def clc_prob(df: pd.DataFrame, ):
    rdf = df[df['real_flag'] != 0][['flag', 'price', 'posi', 'ok_p', 'ok_posi', 'hh', 'll', 'hhp', 'llp',
                                    'f_1', 'price_1', 'posi_1', 'ok_price_1', 'ok_posi_1', 'f_2', 'price_2',
                                    'posi_2', 'ok_price_2', 'ok_posi_2', 'f_3', 'price_3', 'posi_3',
                                    'ok_price_3', 'ok_posi_3', 'f_4', 'price_4', 'posi_4', 'ok_price_4',
                                    'ok_posi_4', 'f1', 'price1', 'ok_price1', 'real_flag',
                                    'uda', 'udr', 'pv4']]
    rdf = rdf[rdf.pv4 != 0]  # 只保留第4 个以后的峰或谷的结果，前3个的pv4==0
    rdf['diff_ok_p1'] = -rdf.ok_p.diff(-1)  # 前后2个确认点的价差
    rdf['diff_price1'] = -rdf.price.diff(-1)
    rdf['g_2'] = 0  # 未来收益的二分组，有可能存在3组＋，0，——
    rdf.loc[rdf.diff_ok_p1 > 0, 'g_2'] = 1
    rdf.loc[rdf.diff_ok_p1 < 0, 'g_2'] = -1
    if rdf.empty or len(rdf) < 2:
        return rdf, None
    trades = rdf.groupby(['f_4', 'f_3', 'f_2', 'f_1', 'flag', 'pv4', 'udr', 'g_2'])['diff_ok_p1'].describe()
    # if trades.empty:
    #     return None, None
    # print('trades:\n',trades)
    c = trades['count'].unstack(-1).fillna(0)  # 将 g_2的值（－1，0，1）变为列名，对应的
    m = trades['mean'].unstack(-1).fillna(0)
    sc = c.sum(1)  # 按列汇总 次数  axis='columns'

    prof = m * c  # 均值＊次数
    sum_prof = prof.sum(axis='columns')  # 按列汇总 收益

    prob = c.truediv(sc, axis='index')  # [0,1]
    prob['count'] = sc
    prob['total_prof'] = sum_prof
    prob['short_mean'] = m[-1] if -1 in m.columns else 0
    prob['money_mean'] = m[0] if 0 in m.columns else 0
    prob['long_mean'] = m[1] if 1 in m.columns else 0
    prob.reset_index(inplace=True)
    if -1 in prob.columns:
        prob.rename(columns={-1: 'short'}, inplace=True)
    else:
        prob['short'] = 0

    if 0 in prob.columns:
        prob.rename(columns={0: 'money'}, inplace=True)
    else:
        prob['money'] = 0
    if 1 in prob.columns:
        prob.rename(columns={1: 'long'}, inplace=True)
    else:
        prob['long'] = 0
    return rdf, prob


def pv_new_tick(pv: pd.DataFrame, last_price, posi, rng=5, wnds=360):
    """根据已知的pv信息，和最新的价格 last_price，位置 和最小周期窗口　windows,计算新的峰和谷
    :param pv: 已经计算好的 峰谷数据, columns=['f_4', 'f_3', 'f_2', 'f_1', 'flag', 'pv4','g_2',
    'short', 'money', 'long', 'count', 'total_prof', 'short_mean', 'money_mean', 'long_mean','udr',
    'price','posi','ok_p','ok_posi','ok_p_5','ok_posi_5','ok_p_4','ok_posi_4','ok_p_3','ok_posi_3',
    'ok_p_2','ok_posi_2','ok_p_1','ok_posi_1','diff_ok_p4','diff_ok_p3','diff_ok_p2','diff_ok_p1'
       ]
    :param last_price: 最新的价格
    :param posi: 最新数据所处的位置（相对于所有用到的数据）
    :param rng:  (p-v)/(p+v)/2*1000 的最小要求，5就是千分之五
    :param wnds: 最小的窗口周期数
    :param rdf: 过去的 真实 pv,一峰一谷，峰谷相间
    :param prob: 过去数据（包括非最终pv)的统计分析结果 ，包括以下列
    ['f_4', 'f_3', 'f_2', 'f_1', 'flag', 'pv4', 'udr', 'g1', 'short', 'money',
       'long', 'count', 'total_prof', 'short_mean', 'money_mean', 'long_mean']
       'f_4', 'f_3', 'f_2', 'f_1', 'flag', 'pv4', 'udr'：是当前的分类方式
       'short', 'money', 'long',：是空、币、多的发生频率，不含最后一条数据的，因为最后的真实结果未知
    :return : np.array(pv_flag,pv,pv_position，ok_price,ok_position)
    """
    # print(pv, len(pv))
    #
    lst_pv = pv.iloc[-1]
    cur_pv_flag, p0, v0, pp0, vp0, xprice, xposi = lst_pv[['flag', 'hh', 'll', 'hhp', 'llp', 'price', 'posi']]
    rslt = clc_pv_tick(p0, v0, last_price, pp0, vp0, posi, cur_pv_flag, xprice, xposi, rng, wnds)
    # print('参数：', p0, v0, last_prices[i], pp0, vp0, i, cur_pv_flag, xprice, xposition)
    p, v, pp, vp, pv_flag, xprice, xposi = rslt
    # print('结果：', rslt)
    # bs = 0  # 买卖方向：1＝＝多，－1＝＝空，0＝＝币
    # position = 0.  # 头寸百分比
    # stop_price = 0.  # 止损价位
    # uda0, udr0, zc = 0, 0, 0
    if cur_pv_flag != pv_flag:
        new = {'flag': pv_flag, 'price': xprice, 'posi': xposi, 'ok_p': last_price,
               'ok_posi': posi, 'hh': p, 'll': v, 'hhp': pp, 'llp': vp}

        pv = pv.append(new, ignore_index=True)

        return pv
    else:  # 没有新的峰谷被确认
        idx = pv.tail(1).index[0]
        pv.loc[idx, ['hh', 'll', 'hhp', 'llp']] = [p, v, pp, vp]
        # return pv, rdf, prob, bs, position, stop_price
        return pv


def set_flag(pv: pd.DataFrame):
    """设置各类标签"""
    for i in range(1, 5):
        sf = f'f_{i}'
        pv[sf] = pv.flag.shift(i)
        sp = f'price_{i}'
        pv[sp] = pv.price.shift(i)
        spp = f'posi_{i}'
        pv[spp] = pv.posi.shift(i)
        sop = f'ok_price_{i}'
        pv[sop] = pv.ok_p.shift(i)
        dop = f'diff_ok_p{i}'
        pv[dop] = -pv.ok_p.diff(-i)
        soposi = f'ok_posi_{i}'
        pv[soposi] = pv.ok_posi.shift(i)
    pv['flag_m_p'] = 0  # 根据flag的正负号分类
    pv.loc[pv.flag < 0, 'flag_m_p'] = -1
    pv.loc[pv.flag > 0, 'flag_m_p'] = 1
    x = pv['flag_m_p'][::-1].rolling(window=2, min_periods=1).sum()[::-1] + pv['flag_m_p']
    pv['real_flag'] = 0
    pv.loc[x == 1, 'real_flag'] = 1
    pv.loc[x == -1, 'real_flag'] = -1

    real_flag = pv[pv['real_flag'] != 0][['real_flag', 'price', 'ok_p', 'flag']]
    real_flag['ud'] = abs(real_flag['price'].diff(1))  # 峰（谷）的高（深）度
    # 计算升段、降段间的关系，并分类
    udr = [0, 0]  # 分类：1,2,...,6,-1,-2,...,-6
    uda = [0, 0]  # 关系:-1,0,1
    d0 = 0
    u0 = 0
    if real_flag.iloc[0]['real_flag'] == 1:
        d0 = real_flag.iloc[1]['ud']
    else:
        u0 = real_flag.iloc[1]['ud']

    for i in range(2, len(real_flag)):
        if u0 <= 0:
            u0 = real_flag.iloc[i]['ud']
            a0, r0 = HLRelationship.clc_udra(d0, u0)
            udr.append(r0)
            uda.append(a0)
            d0 = 0
        else:
            d0 = real_flag.iloc[i]['ud']
            a0, r0 = HLRelationship.clc_udra(d0, u0)
            udr.append(r0)
            uda.append(a0)
            u0 = 0
    real_flag['uda'] = uda
    real_flag['udr'] = udr
    pv['uda'] = real_flag['uda']
    pv['udr'] = real_flag['udr']

    # 对 连续4个PV进行分类
    pv4 = [0, 0, 0]
    dl = len(real_flag)
    if dl >= 4:
        for i in range(3, dl):
            zc = clc_zc(real_flag.iloc[i - 3]['price'], real_flag.iloc[i - 2]['price'],
                        real_flag.iloc[i - 1]['price'],
                        real_flag.iloc[i]['price'])
            pv4.append(zc)
    real_flag['pv4'] = pv4

    pv['pv4'] = real_flag['pv4']
    pv['pv4']=pv['pv4'].fillna(method='ffill')
    pv['pre_pv4'] = pv['pv4'].shift(1)
    return pv


def best_classifier(pv, target='t_prof', c_fields=None, g_fields=None, max_g_fs=4):
    """搜索最优的分类方式
    :param pv:
    :param target: 最优的评价列，可能的列有：
    t_prof, avg_prof,prof_rate
    :param g_fields: 分类列名称，可能的分类列有：
    f_4,f_3,f_2,f_1,flag,pv4,pre_pv4
    :param max_g_fs: 最大分类列数
    """
    result = []

    cfs = ['diff_ok_p1', 'diff_ok_p2', 'diff_ok_p3', 'diff_ok_p4'] if c_fields is None else c_fields
    g_fields = ['flag', 'pv4', 'pre_pv4', 'udr', 'f_1', 'f_2', 'f_3', 'f_4'] if g_fields is None else g_fields

    gfs = make_sets(g_fields, max_g_fs)
    for cf in cfs:
        # t_prof = []
        # avg = []
        for gf in gfs:
            s1 = classifier_stat(pv, g_fields=gf, cri_field=cf)
            t_prof1 = s1.t_prof.abs().sum()
            # t_prof.append({','.join(gf): t_prof1})
            t_c = s1.t_count.sum()
            # avg.append({','.join(gf): t_prof1 / t_c})
            result.append([','.join(gf), len(gf), cf, t_prof1, t_prof1 / t_c])
    df = pd.DataFrame(data=result, columns=['flag', 'flags', 'cri', 'prof', 'avg'])
    dfg = df.groupby(['flags', 'cri'])
    t_b = dfg.prof.max()
    # a_b=dfg.avg.max()
    result = []
    r0 = pd.DataFrame(columns=['flag', 'flags', 'cri', 'prof', 'avg'])
    for (flags, cri) in t_b.index:
        # print(flags,cri)
        prof = t_b.loc[(flags, cri)]
        r = df.query(f'flags=={flags} and cri=="{cri}" and prof=={prof}')
        # print(cri,r)
        r0 = r0.append(r, ignore_index=True)
        result.append([cri, flags, classifier_stat(pv, r.flag.iloc[0].split(','), cri)])

    return result, t_b


def make_sets(lst, members=0):
    """利用lst的元素生成所有可能的非空集合
    :params lst: 元素列表
    :params members 0 or a number or a list : 要求的元素数目，＝＝0，所有可能 ，若为列表，则只要求列表中元素个数的集合
    """
    a = lst
    b = [[lst[0]]]
    if members == 0:
        members = list(range(len(a)))
    elif isinstance(members, int):
        for i in range(min(members + 1, len(a))):
            b0 = deepcopy(b)
            # print(f'{i}:\n{b}\n{b0}\n')
            for v in b:
                v.append(a[i])
                # print(v)
                # b0.append(v)
            b.append([a[i]])
            b += b0
        return [v for v in b if len(v) == members]
    for i in members:
        b0 = deepcopy(b)
        # print(f'{i}:\n{b}\n{b0}\n')
        for v in b:
            v.append(a[i])
            # print(v)
            # b0.append(v)
        b.append([a[i]])
        b += b0
    return b


def clc_zc(pv0, pv1, pv2, pv3):
    """
    根据给定的两峰两谷的值及其顺序，确定这连续的4个峰谷的类型
    :param pv0: 第一个峰谷
    :param pv1: 第二个峰谷
    :param pv2: 第三个峰谷
    :param pv3: 第四个峰谷
    :return: KLine4Type 中的某一个值
    """
    if pv0 > pv1:  # 峰先
        p0, v0, p1, v1 = pv0, pv1, pv2, pv3

        if p1 > p0:
            if v1 > v0:
                c = HLRelationship.PGPVGP if v1 > p0 else HLRelationship.PGPVGV
            elif v1 == v0:
                c = HLRelationship.PGPVEV
            else:
                c = HLRelationship.PGPVLV
        elif p1 == p0:
            if v1 > v0:
                c = HLRelationship.PEPVGV
            elif v1 == v0:
                c = HLRelationship.PEPVEV
            else:
                c = HLRelationship.PEPVLV
        else:
            if v1 > v0:
                c = HLRelationship.PLPVGV
            elif v1 == v0:
                c = HLRelationship.PLPVEV
            else:
                c = HLRelationship.PLPVLV
    else:  # 谷先
        v0, p0, v1, p1 = pv0, pv1, pv2, pv3
        if p1 > p0:
            if v1 > v0:
                c = HLRelationship.VGVPGP
            elif v1 == v0:
                c = HLRelationship.VEVPGP
            else:
                c = HLRelationship.VLVPGP
        elif p1 == p0:
            if v1 > v0:
                c = HLRelationship.VGVPEP
            elif v1 == v0:
                c = HLRelationship.VEVPEP
            else:
                c = HLRelationship.VLVPEP
        else:
            if v1 > v0:
                c = HLRelationship.VGVPLP
            elif v1 == v0:
                c = HLRelationship.VEVPLP
            else:
                c = HLRelationship.VLVPLP if v0 > p1 else HLRelationship.VLVPLV
    return c


def clc_pc(h0, l0, hp0, lp0, hh, ll, hhp, llp, windows, rng, cur_pc, cur_pv_flag):
    """
    根据已知的状态，及最新的高、低（或last_price),计算出新的状态:
    震荡：SSC
    从一个峰或谷开始，在windows周期内的波动小于rng.
    涨：UP
    跌：DN
    涉及的时点：
    峰、谷的发生和确认
    三种状态的开始、确认、结束

    :param h0: new high
    :param l0: new low
    :param hp0: new high position
    :param lp0: new low position
    :param hh: pre-high
    :param ll: pre-low
    :param llp: pre-low position
    :param hhp: pre-high position
    :param windows: window length
    :param rng: percent range
    :param cur_pc: current price class
    :param cur_pv_flag: current peak-valley flag，取值于　HL_Relationship.[W*,P*,]
    :return [hh,ll,hhp,llp,pc,h,l,hp,lp,pv,po,ppo]=[最高，最低，最高位置，最低位置，价格关系，高，低，高位置，低位置，
    当前确认的峰谷,确认价格，确认位置]

    """
    if cur_pc == HLRelationship.PCUN:  # unknown pc
        hh_ll_periods = max(hp0, lp0)
        if h0 > hh:
            hh = h0
            hhp = hp0
            if (hh - ll) * 200. / (hh + ll) > rng:  # 底被确认，从此后，在顶被确认前，再创新高，就是涨的继续
                pc = HLRelationship.UB
                pv = HLRelationship.UB
                return [h0, ll, hp0, llp, pc, h0, l0, hp0, lp0, pv, h0, hp0]

            elif hh_ll_periods == windows:  # 震荡势确认
                pc = HLRelationship.SS

                scp = (hh + ll) / 2
                sch = scp * (1 + rng / 100.)
                scl = scp * (1 - rng / 100.)
                if hp0 > lp0:  # 基于周期窗口windows的谷被确认
                    lll = ll
                    llp0 = lp0
                    ll = l0
                    llp = lp0
                    cpv = 1
                    pv = HLRelationship.WVO  # windows
                    return []

                else:  # 基于周期窗口windows的峰被确认
                    hhh = hh
                    hhp0 = hp0
                    hh = h0
                    hhp = hp0
                    cpv = -1
            else:
                if h0 > hh:
                    return []
                elif l0 < ll:
                    return []
                else:
                    return []

        elif l0 < ll:
            ll = l0
            llp = lp0
            if (hh - ll) * 200. / (hh + ll) > rng:  # 底被确认，从此后，在顶被确认前，再创新高，就是涨的继续
                pc = HLRelationship.DB
                ll.append([hh, hp0])

                hhh = hh
                hh = h0
                hhp = hp0
                hh_ll_periods = 0
                cpv = 1
            elif hh_ll_periods == windows:
                pc = HLRelationship.SS
                scp = (hh + ll) / 2
                sch = scp * (1 + rng / 100.)
                scl = scp * (1 - rng / 100.)
                if hp0 > lp0:
                    cpv = 1
                else:
                    cpv = -1

        else:
            if hh_ll_periods == windows:
                pc = HLRelationship.SS

                scp = (hh + ll) / 2
                sch = scp * (1 + rng / 100.)
                scl = scp * (1 - rng / 100.)
                if hp0 > lp0:
                    cpv = 1
                else:
                    cpv = -1

    elif cur_pc == HLRelationship.SS:
        if h0 > hh:
            hh = h0
            hhp = hp0
        if l0 < ll:
            ll = l0
            llp = lp0
        if (hh - ll) * 200 / (hh + ll) <= rng:
            return [hh, ll, hhp, llp, cur_pc]
        elif llp > hhp:
            if llp - hhp >= windows:
                return [hh, ll, hhp, llp, HLRelationship.SD]
            else:
                return [hh, ll, hhp, llp, HLRelationship.DB]
        elif llp < hhp:
            if hhp - llp >= windows:
                return [hh, ll, hhp, llp, HLRelationship.SU]
            else:
                return [hh, ll, hhp, llp, HLRelationship.UB]
    elif cur_pc >= HLRelationship.SU:  # 横盘的升势
        if h0 > hh:
            hh = h0
            hhp = hp0
            return [hh, hh, hhp, hhp, HLRelationship.UL]
        elif l0 < ll:
            ll = l0
            llp = lp0
            if (hh - ll) * 200 / (hh + ll) <= rng:
                if llp - hhp < windows:
                    return [hh, ll, hhp, llp, cur_pc]
                else:
                    return [hh, ll, hhp, llp, HLRelationship.SS]
            elif llp - hhp >= windows:
                return [hh, ll, hhp, llp, HLRelationship.SD]
            else:
                return [ll, ll, llp, llp, HLRelationship.DB]
    elif cur_pc <= HLRelationship.SD:
        if l0 < ll:
            ll = l0
            llp = lp0
            return [ll, ll, llp, llp, HLRelationship.DL]
        elif h0 > hh:
            hh = h0
            hhp = hp0
            if (hh - ll) * 200 / (hh + ll) <= rng:
                if hhp - llp < windows:
                    return [hh, ll, hhp, llp, cur_pc]
                else:
                    return [hh, ll, hhp, llp, HLRelationship.SS]
            elif hhp - llp >= windows:
                return [hh, ll, hhp, llp, HLRelationship.SU]
            else:
                return [hh, hh, hhp, hhp, HLRelationship.UB]
    elif cur_pc == HLRelationship.SDI:  # 同一横盘内的降势
        return []
    elif cur_pc == HLRelationship.SUI:  # 同一横盘内的升势
        return


def pc_np(h: np.ndarray, l: np.ndarray, windows: int, rng: float):
    """
    :param h: high array or list or pd.Series
    :param l: low (may be equal to h) array or list or pd.Series
    :param windows: window length
    :param rng: percent range
    """
    dl = len(h)
    if dl < windows:
        return None
    pc = []
    hh = []
    ll = []
    i = 0
    h0, l0 = h[i], l[i]
    hh0 = h0
    hp0 = i
    ll0 = l0
    lp0 = i
    hhp0 = i
    llp0 = i
    hh.append([hh0, hp0])
    ll.append([ll0, lp0])
    pc.append(HLRelationship.PCUN)
    cpc = pc[0]  # current price class
    hh_ll_periods = 0
    cpv = 0  # 当前峰谷：1＝＝峰，－1＝＝谷
    scp = 0  # 震荡确认时高低价的中间价(h0+l0)/2
    sch = 0  # 震荡确认时高低价的顶(h0+l0)/2 *(1+rng/100)
    scl = 0  # 震荡确认时高低价的底(h0+l0)/2 *(1-rng/100)
    for i in range(1, dl):
        hh_ll_periods += 1
        if cpc is not None:
            if cpc == HLRelationship.SS:
                if h[i] > h0:
                    h0 = h[i]
                    hp0 = i
        else:
            if h[i] > h0:
                h0 = h[i]
                hp0 = i
                if (h0 - l0) * 200. / (h0 + l0) > rng:  # 底被确认，从此后，在顶被确认前，再创新高，就是涨的继续
                    cpc = HLRelationship.UB
                    ll0 = l0
                    llp0 = lp0
                    ll.append([ll0, llp0])
                    pc.append(cpc)
                    l0 = l[i]
                    lp0 = i
                    hh_ll_periods = 0
                    cpv = -1
                elif hh_ll_periods == windows:
                    cpc = HLRelationship.SS
                    pc.append(cpc)
                    scp = (h0 + l0) / 2
                    sch = scp * (1 + rng / 100.)
                    scl = scp * (1 - rng / 100.)
                    if hp0 > lp0:
                        ll0 = l0
                        llp0 = lp0
                        l0 = l[i]
                        lp0 = i
                        cpv = 1

                    else:
                        hh0 = h0
                        hhp0 = hp0
                        h0 = h[i]
                        hp0 = i
                        cpv = -1

            elif l[i] < l0:
                l0 = l[i]
                lp0 = i
                if (h0 - l0) * 200. / (h0 + l0) > rng:  # 底被确认，从此后，在顶被确认前，再创新高，就是涨的继续
                    cpc = HLRelationship.DB
                    ll.append([h0, hp0])
                    pc.append(cpc)
                    hh0 = h0
                    h0 = h[i]
                    hp0 = i
                    hh_ll_periods = 0
                    cpv = 1
                elif hh_ll_periods == windows:
                    cpc = HLRelationship.SS
                    pc.append(cpc)
                    scp = (h0 + l0) / 2
                    sch = scp * (1 + rng / 100.)
                    scl = scp * (1 - rng / 100.)
                    if hp0 > lp0:
                        cpv = 1
                    else:
                        cpv = -1

            else:
                if hh_ll_periods == windows:
                    cpc = HLRelationship.SS
                    pc.append(cpc)
                    scp = (h0 + l0) / 2
                    sch = scp * (1 + rng / 100.)
                    scl = scp * (1 - rng / 100.)
                    if hp0 > lp0:
                        cpv = 1
                    else:
                        cpv = -1

    return pc


def classify_price_series(s: pd.Series, windows: int, rng: float):
    """根据给定的窗口长度 windows 和 涨跌幅度千分比 rng，对价格序列 s 进行分类
    :param s: 价格时间序列（tick.LastPrice),type:pd.Series or np.ndarray(1)
    :param windows: 确定PV（峰、谷）的最小周期数,abs(P_p-P_v)>=windows
    :param rng: PV(峰谷)间的最少距离，千分比,(p-v)/(p+v)*2*1000>=rng
    :return: pd.DataFrame(columns=[''])
    """
    if len(s) < windows:
        return
    if not isinstance(s, pd.Series):
        s = pd.Series(s)
    df = pd.DataFrame()
    dl = len(s)
    s = s.apply(np.log)  # 取对数后，差值即为变化率
    hh = s.rolling(windows * 2 + 1, min_periods=windows).max()
    ll = s.rolling(windows * 2 + 1, min_periods=windows).min()
    d_hh = hh.diff(1) * 1000.
    d_ll = ll.diff(1) * 1000.
    d_hh.fillna(0, inplace=True)
    d_ll.fillna(0, inplace=True)
    sd_hh = pd.Series([0] * dl, index=s.index)
    sd_hh[d_hh > 0] = 1  # 创新高
    sd_hh[d_hh < 0] = -1
    sdhh1 = sd_hh[sd_hh != 0]
    sdhh11 = sdhh1[sdhh1.rolling(2).sum() == 0]  # 剔除连续升或连续降的，保留第一次升或降
    sdhh11[sdhh11 > 0] = 4
    sdhh11[sdhh11 < 0] = -1

    sd_ll = pd.Series([0] * dl, index=s.index)
    sd_ll[d_ll > 0] = 1
    sd_ll[d_ll < 0] = -1
    sdll1 = sd_ll[sd_ll != 0]
    sdll11 = sdll1[sdll1.rolling(2).sum() == 0]  # 剔除连续升或连续降的，保留第一次升或降
    sdll11[sdll11 > 0] = 1
    sdll11[sdll11 < 0] = -4
    # 高低价间的幅度
    d_hl = (hh - ll) * 1000.
    # hh与ll间的幅度是否大于rng
    sd_hl = pd.Series([0] * dl, index=s.index)
    sd_hl[d_hl >= rng] = 1  # 满足涨跌幅的要求

    df['hh'] = hh
    df['ll'] = ll
    df['dhh'] = d_hh
    df['dll'] = d_ll
    df['dhl'] = d_hl
    df['sgn_dhl'] = sd_hl
    df['sgn_dhh'] = sd_hh
    df['sgn_dll'] = sd_ll
    df['sdll11'] = sdll11
    df['sdhh11'] = sdhh11
    df.fillna(0, inplace=True)
    df['sdhhll11'] = sdhh11 + sdll11

    sdhl = sd_hh + sd_ll  # 可能的值 -2,-1,0,1,2
    sdhl1 = sdhl[sdhl != 0]  # -2,-1,1,2
    # thh=s.rolling(windows,min_periods=1).apply(np.argmax)
    # tll=s.rolling(windows,min_periods=1).apply(np.argmin)

    # 高低价发生时的间隔周期数

    df['sum_dhh'] = d_hh.rolling(dl, min_periods=1).sum()

    df['sum_dll'] = d_ll.rolling(dl, min_periods=1).sum()

    return df


def tick_zig(last_price: np.array, r=20, start_posi=0, old_zig=None):
    """
    注意：这个算法的最后一个峰/谷是未确定的，在实际应用时要另行处理
    :param last_price: np.array 一个一维数组
    :param r: 要求的最小绝对差值
    :param start_posi:
    :param old_zig: 已经计算好的zig，只有最后一个数据没有参与计算，＝＝None时表示没有计算
    :return: nd.array([[flag,pv,posi]])
    """
    if old_zig is not None:
        current_zig = old_zig[-1]
        flag, value, posi = current_zig
        part_data = last_price[posi:]
        new_zig = tick_zig(part_data, r)
        if new_zig is None:
            return old_zig
        if len(new_zig) >= 2:
            tmp = list(old_zig)
            tmp[-1] = [flag, value, posi]
            tmp.extend(new_zig[1:])
            return np.array(tmp)
        else:
            old_zig[-1] = new_zig[0]
            return old_zig

    p, pp = last_price.max(), last_price.argmax()  # peak,peak position
    v, vp = last_price.min(), last_price.argmin()  # valley,valley position
    if p - v > r:
        if pp > vp:  # 先谷后峰
            pv = [[-1, v, vp + start_posi]]
            # 谷前部分
            if vp > 0:
                new_pv = tick_zig(last_price[0:vp + 1], r, start_posi)
                if new_pv is not None:  # 最后一个是已经在pv中的谷，不要再次加入
                    for i in range(len(new_pv) - 1):
                        pv.insert(i, new_pv[i])
            # 峰谷间部分
            if pp - vp > 2:
                v_o_p = 0
                # 寻找谷的发现点
                for i in range(vp + 1, pp):
                    if last_price - v > r:
                        v_o_p = i
                        break
                if v_o_p > 0 and pp - v_o_p > 1:
                    new_pv = tick_zig(last_price[v_o_p:pp + 1], r, start_posi=v_o_p + start_posi)
                    if new_pv is not None and len(new_pv) > 2:
                        if len(new_pv) % 2 == 0:
                            pv.extend(new_pv[1:-1])
                        else:
                            pv.extend(new_pv[:-1])

            pv.append([1, p, pp + start_posi])
            # 谷后部分
            if pp < len(last_price) - 1:
                new_pv = tick_zig(last_price[pp:], r, start_posi=pp + start_posi)
                if new_pv is not None:
                    pv.extend(new_pv[1:])

        else:  # 先峰后谷
            pv = [[1, p, pp + start_posi]]
            # 峰前部分
            if pp > 0:
                new_pv = tick_zig(last_price[0:pp + 1], r, start_posi=start_posi)
                if new_pv is not None:
                    for i in range(len(new_pv) - 1):
                        pv.insert(i, new_pv[i])
            # 峰谷间部分
            if vp - pp > 1:
                p_o_p = 0
                # 寻找峰的发现点
                for i in range(pp + 1, vp):
                    if p - last_price > r:
                        p_o_p = i
                        break
                if p_o_p > 0 and vp - p_o_p > 1:
                    new_pv = tick_zig(last_price[p_o_p:vp + 1], r, start_posi=p_o_p + start_posi)
                    if new_pv is not None and len(new_pv) > 2:
                        if len(new_pv) % 2 == 0:
                            pv.extend(new_pv[1:-1])
                        else:
                            pv.extend(new_pv[:-1])
            # 加谷
            pv.append([-1, v, vp + start_posi])
            # 峰后部分
            if vp < len(last_price) - 1:
                new_pv = tick_zig(last_price[vp:], r, start_posi=vp + start_posi)
                if new_pv is not None:
                    pv.extend(new_pv[1:])

        return np.array(pv)
    else:
        return None


def pvc_stat(pvc: pd.DataFrame = None, last_prices=None, rng=5, windows=360):
    """计算 pvc 的各种统计结果，如果 pvc is not None, 直接使用 pvc; 否则，利用后三个参数，计算出 pvc 后再进行统计
    :param pvc: pd.DataFrame(columns=['flag', 'price', 'posi', 'ok_p', 'ok_posi', 'rng_p', 'dist_posi']
                pv_flag:HLRelationship.WPI,WPO,WVI,WVO, PPI,PPO,PVI,PVO,P,PO,V,VO
                pvc_flag:HLRelationship.PV4
    :return:
    """
    if not pvc:
        pvc = pv_tick(last_prices, rng, windows)
    df = pvc
    for i in range(1, 5):
        sf = f'f_{i}'
        df[sf] = df.flag.shift(i)
        sp = f'price_{i}'
        df[sp] = df.price.shift(i)
        sop = f'ok_price_{i}'
        df[sop] = df.ok_p.shift(i)
        sdop = f'diff_ok_p_{i}'
        df[sdop] = df.ok_p.diff(i)
    df['diff_price'] = df.price.diff(1)
    df['f1'] = df.flag.shift(-1)
    df['price1'] = df.price.shift(-1)
    df['ok_price1'] = df.ok_p.shift(-1)
    df['diff_ok_p1'] = df.ok_p.diff(-1)
    df['diff_price1'] = df.price.diff(-1)
    # 删除连续同符号的 flag, 保留最后一个，定义为 real_flag， 只分峰＝1，谷＝－1，且峰谷相间
    df['flag_m_p'] = 0
    df.loc[df.flag < 0, 'flag_m_p'] = -1
    df.loc[df.flag > 0, 'flag_m_p'] = 1
    x = df['flag_m_p'][::-1].rolling(window=2, min_periods=1).sum()[::-1] + df['flag_m_p']
    df['real_flag'] = 0
    df.loc[x == 1, 'real_flag'] = 1
    df.loc[x == -1, 'real_flag'] = -1

    real_flag = df[df['real_flag'] != 0][['real_flag', 'price', 'ok_p', 'flag']]
    real_flag['ud'] = abs(real_flag['price'].diff(1))  # 峰（谷）的高（深）度
    # 计算升段、降段间的关系，并分类
    udr = [0, 0]  # 分类：1,2,...,6,-1,-2,...,-6
    uda = [0, 0]  # 关系:-1,0,1
    d0 = 0
    u0 = 0
    if real_flag.iloc[0]['real_flag'] == 1:
        d0 = real_flag.iloc[1]['ud']
    else:
        u0 = real_flag.iloc[1]['ud']

    for i in range(2, len(real_flag)):
        if u0 <= 0:
            u0 = real_flag.iloc[i]['ud']
            a0, r0 = HLRelationship.clc_udra(d0, u0)
            udr.append(r0)
            uda.append(a0)
            d0 = 0
        else:
            d0 = real_flag.iloc[i]['ud']
            a0, r0 = HLRelationship.clc_udra(d0, u0)
            udr.append(r0)
            uda.append(a0)
            u0 = 0
    real_flag['uda'] = uda
    real_flag['udr'] = udr
    df['uda'] = real_flag['uda']
    df['udr'] = real_flag['udr']

    # 对 连续4个PV进行分类
    pv4 = [0, 0, 0]
    dl = len(real_flag)
    if dl >= 4:
        for i in range(3, dl):
            zc = clc_zc(real_flag.iloc[i - 3]['price'], real_flag.iloc[i - 2]['price'], real_flag.iloc[i - 1]['price'],
                        real_flag.iloc[i]['price'])
            pv4.append(zc)
    real_flag['pv4'] = pv4
    real_flag['diff_real_ok_p'] = real_flag['ok_p'].diff(1)
    real_flag['diff_real_ok_p1'] = real_flag['ok_p'].diff(-1)
    df['diff_real_ok_p'] = real_flag['diff_real_ok_p']
    df['diff_real_ok_p1'] = real_flag['diff_real_ok_p1']
    df.loc[:, 'diff_real_ok_p'].fillna(0, inplace=True)
    df.loc[:, 'diff_real_ok_p1'].fillna(0, inplace=True)
    df['pv4'] = real_flag['pv4']

    rdop = df.diff_real_ok_p1
    # 根据未来收益率的正负分为两组
    df['rg_2class'] = 0
    rg_2class = df['rg_2class']
    rg_2class.loc[rdop > 0] = 1
    rg_2class.loc[rdop < 0] = -1
    rgp_2 = df[df['real_flag'] != 0].groupby(['f_3', 'f_2', 'f_1', 'flag', 'pv4', 'udr', 'rg_2class'])[
        'diff_real_ok_p1']
    # 将 g_2class 的值转变为列名称，对应的值是发生的次数，计算以次数决定的频率
    rgp_2count = rgp_2.count()
    rb2 = rgp_2count.unstack(fill_value=0)
    rsum21 = rb2.sum(axis='columns')
    rprb2 = rb2.truediv(rsum21, axis='index')
    # 将 g_2class 的值转变为列名称，对应的值是发生的收益，计算以收益决定的权重
    rgp_2sum = rgp_2.sum()
    rc2 = rgp_2sum.unstack(fill_value=0)
    rsum22 = rc2.sum(axis='columns')
    weightr2 = rc2.truediv(rsum22, axis='index')

    dop = df.diff_ok_p1
    # 根据未来收益率的正负分为两组
    df['g_2class'] = 0
    g_2class = df['g_2class']
    g_2class.loc[dop > 0] = 1
    g_2class.loc[dop < 0] = -1
    gp_2 = df[df['real_flag'] != 0].groupby(['f_3', 'f_2', 'f_1', 'flag', 'pv4', 'udr', 'g_2class'])['diff_real_ok_p1']
    # 将 g_2class 的值转变为列名称，对应的值是发生的次数，计算以次数决定的频率
    gp_2count = gp_2.count()
    b2 = gp_2count.unstack(fill_value=0)
    sum21 = b2.sum(axis='columns')
    prb2 = b2.truediv(sum21, axis='index')
    # 将 g_2class 的值转变为列名称，对应的值是发生的收益，计算以收益决定的权重
    gp_2sum = gp_2.sum()
    c2 = gp_2sum.unstack(fill_value=0)
    sum22 = c2.sum(axis='columns')
    weight2 = c2.truediv(sum22, axis='index')
    # 根据未来收益率由小到大分为10组，1，2，。。。， 10
    df['g_no'] = 0
    g_no = df.g_no
    v0 = dop.quantile(q=0.0)
    for i in range(1, 11):
        v = dop.quantile(q=i / 10.)
        x = df[df.diff_ok_p1 < v]
        y = x.loc[x.query(f'diff_ok_p1>={v0}').index].g_no
        g_no.loc[y.index] = i
        v0 = v
    # 将最大的一组分给第十组
    g_no.loc[df.query(f'diff_ok_p1>={v0}').index] = 10
    gp = df[df['real_flag'] != 0].groupby(['f_3', 'f_2', 'f_1', 'flag', 'pv4', 'udr', 'g_no'])['diff_real_ok_p1']
    # 将 g_no 的值转变为列名称，对应的值是发生的次数，计算以次数决定的频率
    gp_count = gp.count()
    b = gp_count.unstack(fill_value=0)
    sum10 = b.sum(axis='columns')
    prb = b.truediv(sum10, axis='index')
    # 将 g_no 的值转变为列名称，对应的值是发生的收益，计算以收益决定的权重
    gp_sum = gp.sum()
    c = gp_sum.unstack(fill_value=0)
    sum10_2 = c.sum(axis='columns')
    weight = c.truediv(sum10_2, axis='index')

    df['g_no'] = g_no
    df['g_no_1'] = g_no.shift(1)

    return df, prb, weight, prb2, weight2, rprb2, weightr2


def optimise_pvc(last_prices=None, pct10=[5, 20, 1], windows=[360, 7200, 120]):
    """可选用：
    遗传算法参数优化
    多进程参数优化
    :param pvc: pd.DataFrame(columns=['flag', 'price', 'posi', 'ok_p', 'ok_posi', 'rng_p', 'dist_posi']
                pv_flag:HLRelationship.WPI,WPO,WVI,WVO, PPI,PPO,PVI,PVO,P,PO,V,VO
                pvc_flag:HLRelationship.PV4
    """


if __name__ == "__main__":
    data = np.array(
        [122, 100, 180, 78, 95, 75, 65, 70, 66, 68, 70, 81, 80, 88, 89, 60, 52, 58, 51, 60, 70, 80, 77, 75, 72, 75,
         76, 71, 75, 74, 77, 78, 79, 80])
    r = 200
    wnds = 5
    t = pv_tick(data, r, wnds)
    print(data, '\n')

    print(t)
